/**
 * @ignore
 * function utilities of lang
 * @author yiminghe@gmail.com
 */

var util = require('./base');
// ios Function.prototype.bind === undefine
function bindFn(r, fn, obj) {
    function FNOP() {
    }

    var slice = [].slice,
        args = slice.call(arguments, 3),
        bound = function () {
            var inArgs = slice.call(arguments);
            return fn.apply(
                    this instanceof FNOP ? this :
                    // fix: y.x=util.bind(fn);
                    obj || this,
                (r ? inArgs.concat(args) : args.concat(inArgs))
            );
        };
    FNOP.prototype = fn.prototype;
    bound.prototype = new FNOP();
    return bound;
}

util.mix(util, {
    /**
     * empty function
     * @member KISSY
     */
    noop: function () {
    },
    /**
     * Creates a new function that, when called, itself calls this function in the context of the provided this value,
     * with a given sequence of arguments preceding any provided when the new function was called.
     * refer: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
     * @param {Function} fn internal called function
     * @param {Object} obj context in which fn runs
     * @param {*...} var_args extra arguments
     * @member KISSY
     * @return {Function} new function with context and arguments
     */
    bind: bindFn(0, bindFn, null, 0),

    /**
     * Creates a new function that, when called, itself calls this function in the context of the provided this value,
     * with a given sequence of arguments preceding any provided when the new function was called.
     * refer: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/bind
     * @param {Function} fn internal called function
     * @param {Object} obj context in which fn runs
     * @param {*...} var_args extra arguments
     * @member KISSY
     * @return {Function} new function with context and arguments
     */
    rbind: bindFn(0, bindFn, null, 1),

    /**
     * Executes the supplied function in the context of the supplied
     * object 'when' milliseconds later. Executes the function a
     * single time unless periodic is set to true.
     *
     * @param fn {Function|String} the function to execute or the name of the method in
     * the 'o' object to execute.
     *
     * @param [when=0] {Number} the number of milliseconds to wait until the fn is executed.
     *
     * @param {Boolean} [periodic] if true, executes continuously at supplied interval
     * until canceled.
     *
     * @param {Object} [context] the context object.
     *
     * @param [data] that is provided to the function. This accepts either a single
     * item or an array. If an array is provided, the function is executed with
     * one parameter for each array item. If you need to pass a single array
     * parameter, it needs to be wrapped in an array.
     *
     * @return {Object} a timer object. Call the cancel() method on this object to stop
     * the timer.
     *
     * @member KISSY
     */
    later: function (fn, when, periodic, context, data) {
        when = when || 0;
        var m = fn,
            d = util.makeArray(data),
            f,
            r;

        if (typeof fn === 'string') {
            m = context[fn];
        }

        if (!m) {
            util.error('method undefine');
        }

        f = function () {
            m.apply(context, d);
        };

        r = (periodic) ? setInterval(f, when) : setTimeout(f, when);

        return {
            id: r,
            interval: periodic,
            cancel: function () {
                if (this.interval) {
                    clearInterval(r);
                } else {
                    clearTimeout(r);
                }
            }
        };
    },

    /**
     * Throttles a call to a method based on the time between calls.
     * @param {Function} fn The function call to throttle.
     * @param {Object} [context] context fn to run
     * @param {Number} [ms] The number of milliseconds to throttle the method call.
     * Passing a -1 will disable the throttle. Defaults to 150.
     * @return {Function} Returns a wrapped function that calls fn throttled.
     * @member KISSY
     */
    throttle: function (fn, ms, context) {
        ms = ms || 150;

        if (ms === -1) {
            return function () {
                fn.apply(context || this, arguments);
            };
        }

        var last = util.now();

        return function () {
            var now = util.now();
            if (now - last > ms) {
                last = now;
                fn.apply(context || this, arguments);
            }
        };
    },

    /**
     * buffers a call between a fixed time
     * @param {Function} fn
     * @param {Number} ms
     * @param {Object} [context]
     * @return {Function} Returns a wrapped function that calls fn buffered.
     * @member KISSY
     */
    buffer: function (fn, ms, context) {
        ms = ms || 150;

        if (ms === -1) {
            return function () {
                fn.apply(context || this, arguments);
            };
        }
        var bufferTimer = null;

        function f() {
            f.stop();
            bufferTimer = util.later(fn, ms, 0, context || this, arguments);
        }

        f.stop = function () {
            if (bufferTimer) {
                bufferTimer.cancel();
                bufferTimer = 0;
            }
        };

        return f;
    }
});